/**
 * @file    tiny_vec.h
 * @author  Ying-Po Liao <lrobin@neo.tamu.edu>
 * @version 1.0
 *
 * @section This file is the header file for array class
 *
 * CSCE-689 Design Using C++
 * Final Project
 */

#ifndef ARRAY_H
#define ARRAY_H

#include <memory>
#include <vector>

template <typename T, unsigned int DIM>
class array
{
public:
	typedef T		num_type;
	typedef array<T, DIM>	array_type;
	static constexpr unsigned int n_dim = DIM;
	typedef std::vector<T>	storage_type;
	
	typedef array_iterator<T, DIM>		iterator;
	typedef array_iterator<const T, DIM>	const_iterator;
	
	array( const array_storage<DIM>& storage = array_storage<DIM>::c_array() ): storage_(storage) {}
	~array() {}
	
	array( const array<T, DIM>& arr );	//!< copy constructor
	array( array<T, DIM>&& arr );		//!< move constructor
	array& operator=( array<T, DIM>&& arr );//!< move assignment 
	
	array(	std::initializer_list<int> shape, 
		const array_storage<DIM>& storage = array_storage<DIM>::c_array() );

	array(	const tiny_vector<int, DIM>& shape, 
		const array_storage<DIM>& storage = array_storage<DIM>::c_array() );

	array(	const tiny_vector<range, DIM>& range,
		const array_storage<DIM>& storage = array_storage<DIM>::c_array() );
	
	//! for slicing
	array(	array& ref_arr, 
		const tiny_vector<range, DIM>& new_slice );
	
	template <typename... ID_types>
	typename array_helper::array_slice<array_type, ID_types...>::return_type 
	operator()( ID_types... id );
	
	template <typename... ID_types>
	typename array_helper::array_slice<const array_type, ID_types...>::return_type 
	operator()( ID_types... id ) const;
	
	tiny_vector<int, DIM> lbound() const { return storage_.get_base(); }
	int lbound( unsigned int dim ) const { return storage_.get_base()[dim]; }
	
	tiny_vector<int, DIM> ubound() const { return storage_.get_base() + shape_ - 1; }
	int ubound( unsigned int dim ) const { return storage_.get_base()[dim] + shape_[dim] - 1; }
	
	void reference( array<T, DIM>& ref_arr );
	
	void slice( unsigned int dim, range r );

	template <typename... ID_types>
	void resize( ID_types&&... args );
	
	const T* data_first() const;
	T* 	 data_first();

	shared_ptr<storage_type>  data() const	{ return data_p_; }
	shared_ptr<storage_type>& data() 	{ return data_p_; }
	
	int  zero_offset() const { return p_zero_offset_; }
	int& zero_offset()	 { return p_zero_offset_; }

	const array_storage<n_dim>& 	storage() const { return storage_; }
	array_storage<n_dim>& 		storage() 	{ return storage_; }

	const tiny_vector<int, DIM>& 	shape() const 	{ return shape_; }
	tiny_vector<int, DIM>& 		shape() 	{ return shape_; }

	const tiny_vector<int, DIM>& 	stride() const 	{ return stride_; }
	tiny_vector<int, DIM>& 		stride() 	{ return stride_; }

	iterator	begin() 	{ return iterator(*this); }
	const_iterator 	begin() const 	{ return const_iterator(*this); }
	iterator	end() 		{ return iterator(); }
	const_iterator 	end() const 	{ return const_iterator(); }

	array& operator= ( std::initializer_list<T> vals);
	
	array& operator= ( const T const_val );
	array& operator+=( const T const_val );
	array& operator-=( const T const_val );
	array& operator*=( const T const_val );
	array& operator/=( const T const_val );
	array& operator%=( const T const_val );
	array& operator^=( const T const_val );
	array& operator&=( const T const_val );
	array& operator|=( const T const_val );
	array& operator>>=( const int );
	array& operator<<=( const int );
    
	array& operator= ( const array& arr );
	array& operator+=( const array& arr );
	array& operator-=( const array& arr );
	array& operator*=( const array& arr );
	array& operator/=( const array& arr );
	array& operator%=( const array& arr );
	array& operator^=( const array& arr );
	array& operator&=( const array& arr );
	array& operator|=( const array& arr );
	array& operator>>=( const array& arr );
	array& operator<<=( const array& arr );
	
	bool operator==( const array& arr );
	bool operator!=( const array& arr );
	//bool operator >( const array& arr );
	//bool operator <( const array& arr );
	//bool operator>=( const array& arr );
	//bool operator<=( const array& arr );
	
private:
	void create_new_array();
	
public:
	shared_ptr<storage_type> data_p_;
	array_storage<n_dim> storage_;
	tiny_vector<int, DIM> shape_;
	tiny_vector<int, DIM> stride_;
	int p_zero_offset_;
};



#endif //!< ARRAY_H